﻿using System;
using System.Xml;
using System.Xml.Schema;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Microsoft.VisualStudio.XmlEditor;
using Microsoft.VisualStudio.SharePoint;
using Microsoft.VisualStudio.Shell;

namespace CodeCounsel.SharePoint.RibbonDesigner.Model
{
    public abstract class DomainElement
        : INotifyPropertyChanged
    {
        RibbonDocument _owner;
        DomainElementCollection _children;

        protected internal DomainElementCollection Children
        {
            get
            {
                if (_children == null)
                {
                    _children = CreateDomainModelCollection();
                }
                return _children;
            }
        }

        protected internal XElement DataModel { get; private set; }
        public DomainElement Parent { get; private set; }
        public DomainModelSource Source { get; private set; }

        public RibbonDocument Owner
        {
            get
            {
                if (_owner == null && Parent != null)
                {
                    _owner = Parent.Owner;
                }
                return _owner;
            }
        }

        public IEnumerable<CommandUIDefinition> Extensions
        {
            get { return Owner.GlobalModel.GetExtensions(this); }
        }

        protected DomainElement(XElement dataModel, 
            DomainModelSource source)
        {
            if (dataModel == null)
            {
                throw new ArgumentNullException("dataModel");
            }
            XName name =  XNameAttribute.GetName(GetType());
            if (dataModel.Name != name)
            {
                throw new ArgumentException("dataModel");
            }
            DataModel = dataModel;
            Source = source;
        }

        protected DomainElement()
        { 
            DataModel = CreateElement(GetType());
            Source = DomainModelSource.Project;
        }

        protected static XElement CreateElement(Type type)
        {
            XName name = XNameAttribute.GetName(type);
            return new XElement(name);
        }

        public string ToXml()
        {
            return DataModel.ToString();
        }

        public bool GetIsValid()
        {
            IXmlSchemaInfo schemaInfo = DataModel.GetSchemaInfo();
            return schemaInfo != null && schemaInfo.Validity == XmlSchemaValidity.Valid;
        }

        internal void SetParent(DomainElement parent)
        {
            Parent = parent;
            if (parent != null)
            {
                //TODO: Likely move this code:
                if (parent.DataModel != DataModel.Parent)
                {
                    parent.DataModel.Add(DataModel);
                }
            }
        }

        internal void SetDefinition(RibbonDocument ribbonDefinition)
        {
            _owner = ribbonDefinition;
        }

        internal virtual bool IsExtensionForElement(CommandUIDefinition cued)
        {
            return cued.Type == CommandUIExtensionType.Replace ?
                Children
                    .OfType<NamedDomainElement>()
                    .Where(nde => nde.Id == cued.Location)
                    .FirstOrDefault() != null
                    :
                false;
        }

        internal virtual void RefreshChildren()
        {
            Children.Clear();
            Children.AddRange(DomainModelFactory.CreateModelCollection(DataModel, Source).ToArray());
        }

        protected virtual DomainElementCollection CreateDomainModelCollection()
        {
            return new DomainElementCollection(this,
                DomainModelFactory.CreateModelCollection(DataModel, Source));
        }

        void HandleDataModelChange(
            XElement element, XObjectChange change)
        {
            if (change == XObjectChange.Remove)
            {
                HandleRemove(element);
            }
        }

        protected virtual bool HandleRemove(XElement element)
        {
            bool handled = false;
            DomainElement domainElement = Children.Where(c => c.DataModel == element).FirstOrDefault();
            if (domainElement != null)
            {
                Children.Remove(domainElement);
                RaiseDomainElementRemoved(domainElement);
                handled = true;
            }
            return handled;
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        protected virtual void RaiseDomainElementRemoved(DomainElement item)
        {
        }

        void DataModel_Changed(object sender, XObjectChangeEventArgs e)
        {
            XElement senderElement = sender as XElement;
            if (senderElement != null)
            {
                HandleDataModelChange(senderElement, e.ObjectChange);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

    }
}